package com.eshop.eshopcache.zk;

import com.eshop.eshopcache.constant.ProjectCons;
import com.eshop.eshopcache.enums.ZkLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import java.util.concurrent.CountDownLatch;

/**
 * @author: Xukai
 * @description: ZooKeeperSession
 * @createDate: 2018/7/30 14:42
 * @modified By:
 */
@Slf4j
public class ZooKeeperSession {

    /**
     * zookeeper server列表
     */
    private static final String HOSTS = ProjectCons.REMOTE_ZK;

    private CountDownLatch connectedSemaphore = new CountDownLatch(1);

    private ZooKeeper zk;

    private ZooKeeperSession() {
        try {
            zk = new ZooKeeper(HOSTS, ProjectCons.ZK_SESSION_TIMEOUT, event -> {
                try {
                    // 连接建立时, 打开connectedSemaphore
                    // 唤醒wait在该打开connectedSemaphore上的线程
                    if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                        connectedSemaphore.countDown();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            // 等待连接建立
            connectedSemaphore.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("Zookeeper建立连接......");
    }

    /**
     * 获取分布式锁
     *
     * @param id
     */
    public void acquireDistributedLock(String type, Long id) {
        String path = getPath(type, id);
        int tryNum = 1;
        while (true) {
            try {
                zk.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                tryNum++;
                log.info("【尝试获得锁】尝试第 {} 次获得{}锁 [id={}]", tryNum, type, id);
                continue;
            }
            log.info("【获得锁】尝试 {} 次后获得{}锁 [id={}]", tryNum, type, id);
            break;
        }
    }

    /**
     * 尝试快速获取锁，不管是否能获取锁都直接返回
     *
     * @param type
     * @param id
     * @return
     */
    public boolean acquireFastFailedDistributedLock(String type, Long id) {
        String path = getPath(type, id);
        try {
            zk.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            log.info("【快速获得锁】获得{}锁成功 [id={}]", type, id);
            return true;
        } catch (Exception e) {
            log.error("【快速获得锁】获得{}锁失败 [id={}]", type, id);
        }
        return false;
    }

    /**
     * 释放一个分布式锁
     *
     * @param id
     */
    public void releaseDistributedLock(String type, Long id) {
        String path = getPath(type, id);
        try {
            if (zk.exists(path, false) != null) {
                zk.delete(path, -1);
                log.info("【释放锁】释放{}锁成功 [id={}]", type, id);
            } else {
                log.info("【释放锁】{}节点不存在 [id={}]", type, id);
            }
        } catch (Exception e) {
            log.error("【释放锁】释放{}锁失败 [id={}]", type, id);
            e.printStackTrace();
        }
    }

    /**
     * 设置节点的值
     *
     * @param type
     * @param id
     * @param data
     */
    public void setNodeDate(String type, Long id, String data) {
        String path = getPath(type, id);
        try {
            if (zk.exists(path, false) == null) {
                zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            } else {
                zk.setData(path, data.getBytes(), -1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获得节点的值
     *
     * @param type
     * @param id
     * @return
     */
    public String getNodeData(String type, Long id) {
        String path = getPath(type, id);
        try {
            if (zk.exists(path, false) != null) {
                return new String(zk.getData(path, false, new Stat()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private String getPath(String type, Long id) {
        String path;
        if (id == null) {
            path = ZkLock.getNodeByType(type);
        } else {
            path = ZkLock.getNodeByType(type) + id;
        }
        return path;
    }

    /**
     * 封装单例的静态内部类
     */
    private static class Singleton {

        private static ZooKeeperSession instance;

        static {
            instance = new ZooKeeperSession();
        }

        public static ZooKeeperSession getInstance() {
            return instance;
        }
    }

    /**
     * 获取单例
     *
     * @return
     */
    public static ZooKeeperSession getInstance() {
        return Singleton.getInstance();
    }

    /**
     * 初始化单例的便捷方法
     */
    public static void init() {
        getInstance();
    }
}
